import re, os
import time, cStringIO
import posixpath #really for URL path part manipulation

from paste.httpexceptions import *
from paste.wsgiwrappers import WSGIRequest

from datetime import datetime
from dateutil.tz import *

from brightcontent.core import SLUGCHARS
from brightcontent.util import UrlDispatch, get_base_url, fixup_namespaces, quick_xml_multiscan, web_trigger
from brightcontent.core.store import COMMON_PREFIXES, OMIT_FROM_SLUG_PAT
from brightcontent.core.renderer import BC_NS
# from brightcontent.util import get_clean_title

from brightcontent.plugins import staticfiles

from Ft.Xml import XML_NAMESPACE as XML_NS
from Ft.Lib import Uri
from Ft.Xml.Xslt import Transform
from wsgixml.applyxslt import setup_xslt_params
import amara

from pkg_resources import resource_string

entries_per_page = 10

ENTRY_ENVELOPE_XML = 'entry.xml'
POST_COLLECTION_TRIGGER = '/bctriggers/post-collection?id=%s'

class WsgiAtomPublishingProtocol(object):
    def __init__(self, application, store, app_conf):
        self.m = UrlDispatch(self.urls)
        self.entry_m = UrlDispatch(self.entry_urls)
        self.application = application
        self.store = store
        #For most people the name exposed by the store will be
        #sufficient for the collection name
        self.collection_name = app_conf.get('atom_collection_name', self.store.name)
        if app_conf.get('atom_default_collection', None):
            self.default_collection = app_conf['atom_default_collection']
        # this is temporary until the config middleware is working correctly
        self.app_conf = app_conf
        self.weblog_base_url = app_conf.get('weblog_base_url', '')

    def service(self, environ, start_response, method, **kw):
        def GET():
            #TODO: implement a service document based on either the
            #      config or discovery from store(s)
            try:
                servicedoc = file(self.app_conf.get('atom_service_doc', ''), 'r').read()
                start_response('200 OK', [('content-type', 'application/atomserv+xml')])
                return [servicedoc]
            except:
                return HTTPNotImplemented()(environ, start_response)
        allowed_methods = {'GET': GET}
        if method in allowed_methods:
            return allowed_methods[method]()
        else:
            return HTTPMethodNotAllowed()(environ, start_response)

    def entry(self, environ, start_response, method, **args):
        collection = args.get('collection', None)
        entry = args.get('entry', None)
        if self.default_collection:
            collection = self.default_collection
            
        if collection != self.collection_name:
            return HTTPNotFound()(environ, start_response)

        #Use closures rather than inner classes so we can peserve the "scope" of self
        def GET():
            return self.get_entry(collection, entry, environ, start_response)
            
        def PUT():
            return self.put_entry(collection, entry, environ, start_response)
        
        allowed_methods = {'GET': GET, 'PUT' : PUT}
        if method in allowed_methods:
            return allowed_methods[method]()
        else:
            return HTTPMethodNotAllowed()(environ, start_response)

    def collection(self, environ, start_response, method, **args):
        collection_name = args.get('collection', None)
        if collection_name != self.collection_name:
            return HTTPNotFound()(environ, start_response)

        #Use closures rather than inner classes so we can preserve "self" in scope
        def GET():
            return self.get_collection(environ, start_response)

        def POST():
            return self.post_collection(environ, start_response)

        allowed_methods = {'GET': GET, 'POST' : POST}
        if method in allowed_methods:
            return allowed_methods[method]()
        else:
            return HTTPMethodNotAllowed()(environ, start_response)

    def get_entry(self, collection, entry, environ, start_response):
        """
        Handles GET Request for an Entry Resource

        The parameters are *not* keywords b/c this is the only interface to the
        resource. One URL => One Resource
        """
        iri_id = posixpath.join(self.app_conf['weblog_base_url'], collection, entry)
        entries = self.store.get_entry_by_id(iri_id)
        if entries:
            temp = open(entries[0], 'r')
            entries = [temp.read()]
            temp.close()
            start_response('200 OK', [('Content-type', 'application/atom+xml;type="entry"')])
            return entries
        return HTTPNotFound()(environ, start_response)

    def put_entry(self, collection, entry, environ, start_response):
        """
        Updates a resource at the requested URI
        """
        func, query_dict = self.entry_m.match(entry)
        query_date = datetime(int(query_dict['year']), int(query_dict['month']), int(query_dict['day']))
        id_string = posixpath.join(query_date.strftime('%Y/%m/%d'), query_dict['slug'])
        if not self.default_collection:
            iri_id = posixpath.join(self.app_conf['weblog_base_url'], self.collection, id_string)
        else:
            iri_id = posixpath.join(self.app_conf['weblog_base_url'], id_string)
        cur_entry = self.store.get_entry_by_id(iri_id)
        if len(cur_entry):
            entry_file = environ.get('wsgi.input', '')
            length = int(environ.get('CONTENT_LENGTH', 0))
            if length > 0:
                entry_string = entry_file.read(length)

                e = open(cur_entry[0], 'r')
                old_entry = e.read()
                e.close()
                new_entry = app_update(
                    entry_string,
                    amara.parse(old_entry, prefixes=COMMON_PREFIXES)
                )

                entry_file = open(cur_entry[0], 'w')
                entry_file.write(new_entry)
                entry_file.close()
                # self.store.update_entry(str(entry), iri_id)
                temp = open(cur_entry[0], 'r')
                entry = [temp.read()]
                temp.close()
                start_response('200 OK',
                    [('Content-type', 'application/atom+xml;type="entry";charset="utf-8"'),
                     ('Location', iri_id)])
                return entry
            else:
                return HTTPNotAcceptable()(environ, start_response)
        return HTTPNotFound()(environ, start_response)

    def post_collection(self, environ, start_response):
        """
        Adds a new resource to the store
        """
        entry_file = environ.get('wsgi.input', '')
        slug = environ.get('HTTP_SLUG', None)
        if slug:
            slug = OMIT_FROM_SLUG_PAT.sub('_', slug).decode('utf-8')
        length = int(environ.get('CONTENT_LENGTH', 0))
        entry_string = entry_file.read(length)
        new_entry = app_create(
            entry_string,
            self.app_conf['weblog_base_url'],
            # pass in collection url in case we end up getting media entries later
            collection = posixpath.join(get_base_url(environ), environ['PATH_INFO']),
            slug=slug,
            weblog_base_url=self.weblog_base_url,
            # preserve_dates=preserve_dates
        )
        iri_id = amara.parse(new_entry, prefixes=COMMON_PREFIXES)
        iri_id = str(iri_id.entry.id)
        self.store.create_entry(new_entry, slug)
        #web_trigger(environ, POST_COLLECTION_TRIGGER%entry_filename)
        start_response('201 Created', [('Content-type', 'application/atom+xml; charset="utf-8"'),
                                       ('Location', iri_id)])
        return new_entry
        
    def get_collection(self, environ, start_response):
        """
        BC is frontend to one collection.
        """
        #FIXME: Change store(?) to allow getting entries from another directory
        offset = 0
        limit = entries_per_page
        entries = self.store.get_entries(offset=offset, limit=limit)
        start_response('200 OK', [ ('Content-type', 'application/atom+xml') ])
        return [self.store.assemble_feed(entries)]

    def return_entries(self, environ, start_response, entries):
        start_response('200 OK', [('Content-type', 'application/atom+xml')])
        feed = self.store.assemble_feed(entries)
        return [feed]

    def assemble_feed(self, entries, stream=None):
        atom = amara.create_document(
            u'feed', ATOM10_NS, attributes={u'xml:lang': u'en'})
        storedoc = amara.parse(self.envelope_xml_file,
            prefixes=COMMON_PREFIXES)
        for element in storedoc.feed.xml_children:
            atom.feed.xml_append(element)
        for entry in entries:
            atom.feed.xml_append_fragment(entry)
        fixup_namespaces(atom)
        if stream:
            atom.xml(indent='yes', stream=stream)
            return stream
        else:
            buffer = cStringIO.StringIO()
            atom.xml(indent='yes', stream=buffer)
            return buffer.getvalue()

    def __call__(self, environ, start_response):
        path_info = environ['PATH_INFO']
        if not path_info.endswith('/'):
            if not '.' in path_info.split('/').pop():
                new_loc = get_base_url(environ).rstrip('/') + '/'
                if path_info != '/' and path_info != '': 
                    new_loc += path_info.lstrip('/') + '/'
                raise HTTPMovedPermanently(headers=[('location', '%s' % new_loc)])

        # Prepend the default collection to the url
        if self.default_collection:
            qs = environ.get('QUERY_STRING', None)
            if not 'service=true' in qs:
                path_info = posixpath.join('/'+ self.default_collection, path_info.lstrip('/'))
        print path_info
        method = environ.get('REQUEST_METHOD', '')
        func, query_dict = self.m.match(path_info)
        #print path_info, func, query_dict
        if func:
            return func(self, environ, start_response, method, **query_dict)
        else:
            return self.application(environ, start_response)

    urls = (
        (r'/(?P<collection>[a-zA-Z0-9_.:;-=]+)/(?P<entry>\d{4}/\d{1,2}/\d{1,2}/[%s]+.atom)'%(SLUGCHARS), entry),
        (r'/(?P<collection>[a-zA-Z0-9_.:;-=]+)/', collection),
        (r'/?', service),
    )

    entry_urls = (
        (r'(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[%s]+.atom)'%(SLUGCHARS), entry),
    )

SELF_XPATH = u'/atom:entry/atom:link[@rel = "self"][1]'
EDIT_XPATH = u'/atom:entry/atom:link[@rel = "edit"][1]'
PERMALINK_XPATH = u'/atom:entry/atom:link[@title = "permalink"][1]'

def app_update(entry_text, old_entry):
    """
    This requires the new entry *text* and the Old entry as an
    amara *object*
    """
    xslt = resource_string(__name__, 'atomprotocol/update.xslt')
    app_edited = datetime.now()
    params = {}
    params[u'app_edited'] = unicode(app_edited.strftime('%Y-%m-%dT%H:%M:%SZ'))
    selflink = old_entry.xml_xpath(SELF_XPATH)[0]
    editlink = old_entry.xml_xpath(EDIT_XPATH)[0]
    permalink = old_entry.xml_xpath(PERMALINK_XPATH)[0]
    params[u'old_id'] = unicode(old_entry.entry.id)
    params[u'old_selflink'] = unicode(selflink.href)
    params[u'old_editlink'] = unicode(editlink.href)
    params[u'old_permalink'] = unicode(permalink.href)
    return Transform(entry_text, xslt, params=setup_xslt_params(BC_NS, params))

def app_create(entryxml, base_iri, collection, slug='false', weblog_base_url=None, preserve_dates=False):
    # xslt = Uri.OsPathToUri(staticfiles.fget('app_create.xslt'))
    xslt = resource_string(__name__, 'atomprotocol/create.xslt')
    if weblog_base_url:
        base_iri = weblog_base_url
    params = { u'base_url' : unicode(base_iri),
               u'collection' : unicode(collection),
               u'slug' : unicode(slug) }
    return Transform(entryxml, xslt, params=setup_xslt_params(BC_NS, params))


#Only supports flatfile repo for now.  Have to make that configurable
from brightcontent.core.store import flatfile_repository
from paste.deploy.config import ConfigMiddleware
from paste.recursive import RecursiveMiddleware
from paste import httpexceptions 

def make_app(global_conf, **app_conf):
    repository_cname = 'flatfile_repository'
    storedir = app_conf.get('flatfile_storedir', 'entries')
    store = flatfile_repository(flatfile_storedir=storedir)
    # app = ConfigMiddleware(None, app_conf)
    app = WsgiAtomPublishingProtocol(None, store, app_conf)
    # app = RecursiveMiddleware(app, global_conf)
    app = httpexceptions.make_middleware(app)
    return app


